package network.t_03_aio;

import lombok.AllArgsConstructor;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;

/**
 * https://www.jianshu.com/p/2d33ad3e89a6
 * https://www.bilibili.com/video/av43425053
 * https://segmentfault.com/a/1190000020364149
 */
public class AIOServer {
    public static void main(String[] args) {
        try (AsynchronousServerSocketChannel asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open(AsynchronousChannelGroup.withCachedThreadPool(Executors.newFixedThreadPool(8), 1))) {
            asynchronousServerSocketChannel.bind(new InetSocketAddress(6666));
            asynchronousServerSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
                @Override
                public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
                    asynchronousServerSocketChannel.accept(attachment, this); // 处理下次的 client 连接

                    ByteBuffer headBuffer = ByteBuffer.allocate(4);
                    clientChannel.read(headBuffer, headBuffer, new ReadHeaderCompletionHandler(clientChannel));
                }

                @Override
                public void failed(Throwable exc, Object attachment) {
                    exc.printStackTrace();
                }
            });
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @AllArgsConstructor
    static class ReadHeaderCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
        final AsynchronousSocketChannel clientChannel;

        @Override
        public void completed(Integer bytesRead, ByteBuffer buffer) {
            if (buffer.hasRemaining()) {
                clientChannel.read(buffer, buffer, this);
            } else {
                buffer.flip();
                ByteBuffer messageBuffer = ByteBuffer.allocate(buffer.getInt());
                clientChannel.read(messageBuffer, messageBuffer, new ReadMessageCompletionHandler(clientChannel));
            }
        }

        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
            exc.printStackTrace();
        }
    }

    @AllArgsConstructor
    private static class ReadMessageCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
        final AsynchronousSocketChannel clientChannel;

        @Override
        public void completed(Integer bytesRead, ByteBuffer buffer) {
            if (buffer.hasRemaining()) {
                clientChannel.read(buffer, buffer, this);
            } else {
                buffer.flip();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                System.out.println(new String(bytes, StandardCharsets.UTF_8) + "\n收到数据");

                ByteBuffer echoBuffer = ByteBuffer.allocate(4 + bytes.length);
                echoBuffer.putInt(bytes.length);
                echoBuffer.put(bytes);
                echoBuffer.flip();
                clientChannel.write(echoBuffer, echoBuffer, new WriteCompletionHandler(clientChannel));
            }
        }

        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
            exc.printStackTrace();
        }
    }

    @AllArgsConstructor
    static class WriteCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
        final AsynchronousSocketChannel clientChannel;

        @Override
        public void completed(Integer bytesWritten, ByteBuffer buffer) {
            if (buffer.hasRemaining()) clientChannel.write(buffer, buffer, this);
            else System.out.println("发送完毕");
        }

        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
            exc.printStackTrace();
        }
    }
}